﻿
#include "NextH264DecoderCPU.h"
#include "ffmpegdl.h"

namespace videonext
{
	class H264DecoderCPUVars : public Vars
	{
	public:
		Pointer decoder;
		Int sentCount;
		Stream<Double> times;
		Stream<CompatibleImage> frames;
		Size2D frameSize;

		~H264DecoderCPUVars()
		{
			close();
		}
		void close()
		{
			if (decoder)
			{
				avcodec_free_context(&decoder);
				decoder = 0;
			}
		}
	};
}

using namespace videonext;

H264DecoderCPU::H264DecoderCPU() : Object<H264DecoderCPUVars>(new H264DecoderCPUVars, TRUE)
{
	vars->decoder = 0;
}
bool H264DecoderCPU::open(Size2D frameSize)
{
	vars->close();

	Pointer targetCodec = avcodec_find_decoder_by_name("h264");
	if (targetCodec == 0) return false;

	Pointer decoderTmp = avcodec_alloc_context3(targetCodec);
	if (decoderTmp == 0) return false;

	Int resultOpenCodec = avcodec_open2(decoderTmp, targetCodec, NULL);
	if (resultOpenCodec < 0)
	{
		avcodec_free_context(&decoderTmp);
		return false;
	}

	vars->decoder = decoderTmp;
	vars->sentCount = 0;
	vars->times = Stream<Double>(8);
	vars->frames = Stream<CompatibleImage>(8);
	vars->frameSize = frameSize;

	return true;
}
void H264DecoderCPU::close()
{
	vars->close();
}
Bool H264DecoderCPU::send(Binary packetData, Double time)
{
	if (!vars->decoder) return false;

	Pointer packet = NULL;
	if (getFFMPEGLibraryVersion() >= 3)
	{
		packet = av_packet_alloc();
	}
	else
	{
		packet = av_mallocz_av_packet();
	}
	av_new_packet(packet, packetData.size());
	utility::memoryCopy(packetData.data(), av_packet_get_data(packet), packetData.size());

	av_packet_set_pts(packet, vars->sentCount);
	av_packet_set_dts(packet, vars->sentCount);

	Bool ok = FALSE;
	if (getFFMPEGLibraryVersion() >= 3)
	{
		Int resultSendPacket = avcodec_send_packet(vars->decoder, packet);
		ok = resultSendPacket >= 0;

		av_packet_unref(packet);
		av_packet_free(&packet);
	}
	else
	{
		Int gotFrame = 0;
		Pointer frame = av_frame_alloc();
		Int resultDecode = avcodec_decode_video2(vars->decoder, frame, &gotFrame, packet);
		ok = resultDecode >= 0;

		av_packet_unref(packet);
		av_free_packet(packet);
		packet = NULL;

		if (gotFrame)
		{
			if (vars->frameSize.width > 0 && av_frame_get_width(frame) > (Int)vars->frameSize.width) av_frame_set_width(frame, vars->frameSize.width);
			if (vars->frameSize.height > 0 && av_frame_get_height(frame) > (Int)vars->frameSize.height) av_frame_set_height(frame, vars->frameSize.height);

			vars->frames.enqueue(CompatibleImage(frame, TRUE));
		}
		else av_frame_free(&frame);
	}

	if (ok)
	{
		vars->sentCount++;
		vars->times.enqueue(time);
	}

	return ok;
}
void H264DecoderCPU::flush()
{
	if (vars->decoder == 0) return;

	if (getFFMPEGLibraryVersion() >= 3)
	{
		avcodec_send_packet(vars->decoder, NULL);
	}
	else
	{
		Pointer frame = av_frame_alloc();
		Pointer packet = av_mallocz_av_packet();
		av_init_packet(packet);

		Int gotFrame = 0;
		Int resultDecode = avcodec_decode_video2(vars->decoder, frame, &gotFrame, packet);
		if (resultDecode >= 0 && gotFrame)
		{
			if (vars->frameSize.width > 0 && av_frame_get_width(frame) > (Int)vars->frameSize.width) av_frame_set_width(frame, vars->frameSize.width);
			if (vars->frameSize.height > 0 && av_frame_get_height(frame) > (Int)vars->frameSize.height) av_frame_set_height(frame, vars->frameSize.height);

			vars->frames.enqueue(CompatibleImage(frame, TRUE));
		}
		else av_frame_free(&frame);

		av_free_packet(packet);
		packet = NULL;
	}
}
Bool H264DecoderCPU::receive(CompatibleImage& image, Double& time)
{
	if (!vars->decoder) return false;

	if (getFFMPEGLibraryVersion() >= 3)
	{
		Pointer frame = av_frame_alloc();
		if (!frame) return false;

		int resultReceiveFrame = avcodec_receive_frame(vars->decoder, frame);
		if (resultReceiveFrame < 0)
		{
			if (resultReceiveFrame != av_get_error_code_value(AVErrorCode::AVERROR_EAGAIN) && 
				resultReceiveFrame != av_get_error_code_value(AVErrorCode::AVERROR_EOF)) // Hardware failure / 硬件失效
			{
				vars->close();
			}
			av_frame_free(&frame);
			return false;
		}
		else
		{
			if (vars->frameSize.width > 0 && av_frame_get_width(frame) > (Int)vars->frameSize.width) av_frame_set_width(frame, vars->frameSize.width);
			if (vars->frameSize.height > 0 && av_frame_get_height(frame) > (Int)vars->frameSize.height) av_frame_set_height(frame, vars->frameSize.height);

			image = CompatibleImage(frame, TRUE);

			Array<Double> dequeued = vars->times.dequeue(1);
			Bool timeOK = dequeued.size() == 1;
			if (timeOK) time = dequeued[0];

			return image.isValid() && timeOK;
		}
	}
	else
	{
		if (vars->times.isEmpty() || vars->frames.isEmpty()) return FALSE;

		image = vars->frames.dequeue(1)[0];
		time = vars->times.dequeue(1)[0];
		return TRUE;
	}
}
